package queen;

import ga.Individual;
import java.util.Arrays;
import static ga.util.ArrayGenerator.*;

/**
 * This class depicts a condition of the "N Queen Problem".
 *
 * @author <a href="mailto:DL88250@gmail.com">Liang Ding</a>
 * @version 1.0.0.4, Mar 17, 2010
 */
final public class Solution extends Individual {

    private int size;
    private int[] solution;
    private int checkCount;

    public Solution(final int size) {
        this.size = size;
        solution = new int[size];

        for (int i = 0; i < size; i++) {
            solution[i] = i;
        }

        mutate();
    }

    public Solution(final int size, final int[] solution) {
        this.size = size;
        this.solution = solution;

        fit();
    }

    public final int getSize() {
        return size;
    }

    @Override
    public final void mutate() {
        final int swapCount = (int) (Math.random() * 100);

        for (int i = 0; i < swapCount; i++) {
            for (int j = 0; j < size; j++) {
                final int r1 = (int) (Math.random() * size);
                final int r2 = (int) (Math.random() * size);
                swap(solution, r1, r2);
            }
        }

        fit();
    }

    public final Solution crossover(final Individual another) {
        final int[] childSolution = gen(solution, (int[]) another.getGenes());
        final Solution ret = new Solution(size, childSolution);

        return ret;
    }

    @Override
    public int[] getGenes() {
        return solution;
    }

    @Override
    public final void fit() {
        checkCount = countChecks(solution);

        if (0 == checkCount) {
            setFitness(1F);

            return;
        }

        setFitness(1F - (float) checkCount / ((size - 1) * size << 1));
    }

    public static final int countChecks(final int[] solution) {
        int ret = 0;
        /*
         * [0, 1, 2, 3, 4]:
         *
         * 1 0 0 0 0
         * 0 1 0 0 0
         * 0 0 1 0 0
         * 0 0 0 1 0
         * 0 0 0 0 1
         */
        final int size = solution.length;
        for (int row = 0; row < size; row++) {
            final int column = solution[row];

            for (int nextRow = row + 1; nextRow < size; nextRow++) {
                final int nextColumn = solution[nextRow];

                final int rowDiff = Math.abs(row - nextRow);
                final int columnDiff = Math.abs(column - nextColumn);

                if (column == nextColumn || rowDiff == columnDiff) {
                    ret++;
                }
            }
        }

        return ret << 1;
    }

    @Override
    public int compareTo(final Individual o) {
        return getFitness() == o.getFitness() ? (0)
               : (getFitness() < o.getFitness() ? -1 : 1);
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 11 * hash + size;
        hash = 11 * hash + Arrays.hashCode(solution);
        hash = 11 * hash + checkCount;
        hash = 11 * hash + Float.floatToIntBits(getFitness());

        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Solution other = (Solution) obj;
        if (size != other.size) {
            return false;
        }
        if (!Arrays.equals(solution, other.solution)) {
            return false;
        }
        if (checkCount != other.checkCount) {
            return false;
        }
        if (getFitness() != other.getFitness()) {
            return false;
        }

        return true;
    }

    @Override
    public Solution clone() {
        return new Solution(size, solution);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < solution.length; i++) {
            sb.append(solution[i]);
            if (i != solution.length - 1) {
                sb.append(", ");
            }
        }

        return "[" + sb.toString() + "], fitness=" + getFitness()
               + ", check count="
               + checkCount;
    }
}
